/*
 * Copyright (C) 2015-2016 Andrew J. Kroll
 * and
 * Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.
 *
 * This software may be distributed and modified under the terms of the GNU
 * General Public License version 2 (GPL2) as publishe7d by the Free Software
 * Foundation and appearing in the file GPL2.TXT included in the packaging of
 * this file. Please note that GPL2 Section 2[b] requires that all works based
 * on this software must also be made publicly available under the terms of
 * the GPL2 ("Copyleft").
 *
 * Contact information
 * -------------------
 *
 * Circuits At Home, LTD
 * Web      :  https://www.circuitsathome.com
 * e-mail   :  support@circuitsathome.com
 *
 */

#if defined(USB_HOST_SHIELD_H) && !defined(USB_HOST_SHIELD_LOADED)
#define USB_HOST_SHIELD_LOADED

#include <Arduino.h>

#ifndef digitalPinToInterrupt
    #error digitalPinToInterrupt not defined, complain to your board maintainer.
#endif

#if USB_HOST_SHIELD_USE_ISR

  // allow two slots. this makes the maximum allowed shield count TWO
  // for AVRs this is limited to pins 2 and 3 ONLY
  // for all other boards, one odd and one even pin number is allowed.
  static MAX3421E_HOST *ISReven;
  static MAX3421E_HOST *ISRodd;

  static void UHS_NI call_ISReven() {
    ISReven->ISRTask();
  }

  static void UHS_NI call_ISRodd() {
    UHS_PIN_WRITE(LED_BUILTIN, HIGH);
    ISRodd->ISRTask();
  }
#endif

void UHS_NI MAX3421E_HOST::resume_host() {
  // Used on MCU that lack control of IRQ priority (AVR).
  // Resumes ISRs.
  // NOTE: you must track the state yourself!
  #ifdef __AVR__
    noInterrupts();
    if (irq_pin & 1) {
    ISRodd = this;
    attachInterrupt(UHS_GET_DPI(irq_pin), call_ISRodd, IRQ_SENSE);
    } else {
    ISReven = this;
    attachInterrupt(UHS_GET_DPI(irq_pin), call_ISReven, IRQ_SENSE);
    }
    interrupts();
  #endif
}

/* write single byte into MAX3421e register */
void UHS_NI MAX3421E_HOST::regWr(uint8_t reg, uint8_t data) {
  SPIclass.beginTransaction(MAX3421E_SPI_Settings);
  MARLIN_UHS_WRITE_SS(LOW);
  SPIclass.transfer(reg | 0x02);
  SPIclass.transfer(data);
  MARLIN_UHS_WRITE_SS(HIGH);
  SPIclass.endTransaction();
}

/* multiple-byte write                            */

/* returns a pointer to memory position after last written */
uint8_t* UHS_NI MAX3421E_HOST::bytesWr(uint8_t reg, uint8_t nbytes, uint8_t *data_p) {
  SPIclass.beginTransaction(MAX3421E_SPI_Settings);
  MARLIN_UHS_WRITE_SS(LOW);
  SPIclass.transfer(reg | 0x02);
  //printf("%2.2x :", reg);

  while (nbytes) {
    SPIclass.transfer(*data_p);
    //printf("%2.2x ", *data_p);
    nbytes--;
    data_p++; // advance data pointer
  }
  MARLIN_UHS_WRITE_SS(HIGH);
  SPIclass.endTransaction();
  //printf("\r\n");
  return (data_p);
}

/* GPIO write                                           */
/*GPIO byte is split between 2 registers, so two writes are needed to write one byte */

/* GPOUT bits are in the low nybble. 0-3 in IOPINS1, 4-7 in IOPINS2 */
void UHS_NI MAX3421E_HOST::gpioWr(uint8_t data) {
  regWr(rIOPINS1, data);
  data >>= 4;
  regWr(rIOPINS2, data);
  return;
}

/* single host register read    */
uint8_t UHS_NI MAX3421E_HOST::regRd(uint8_t reg) {
  SPIclass.beginTransaction(MAX3421E_SPI_Settings);
  MARLIN_UHS_WRITE_SS(LOW);
  SPIclass.transfer(reg);
  uint8_t rv = SPIclass.transfer(0);
  MARLIN_UHS_WRITE_SS(HIGH);
  SPIclass.endTransaction();
  return (rv);
}
/* multiple-byte register read  */

/* returns a pointer to a memory position after last read   */
uint8_t* UHS_NI MAX3421E_HOST::bytesRd(uint8_t reg, uint8_t nbytes, uint8_t *data_p) {
  SPIclass.beginTransaction(MAX3421E_SPI_Settings);
  MARLIN_UHS_WRITE_SS(LOW);
  SPIclass.transfer(reg);
  while (nbytes) {
    *data_p++ = SPIclass.transfer(0);
    nbytes--;
  }
  MARLIN_UHS_WRITE_SS(HIGH);
  SPIclass.endTransaction();
  return ( data_p);
}

/* GPIO read. See gpioWr for explanation */

/* GPIN pins are in high nybbles of IOPINS1, IOPINS2    */
uint8_t UHS_NI MAX3421E_HOST::gpioRd() {
  uint8_t gpin = 0;
  gpin = regRd(rIOPINS2); // pins 4-7
  gpin &= 0xF0; // clean lower nybble
  gpin |= (regRd(rIOPINS1) >> 4); // shift low bits and OR with upper from previous operation.
  return (gpin);
}

/* reset MAX3421E. Returns number of microseconds it took for PLL to stabilize after reset
or zero if PLL haven't stabilized in 65535 cycles */
uint16_t UHS_NI MAX3421E_HOST::reset() {
  uint16_t i = 0;

  // Initiate chip reset
  regWr(rUSBCTL, bmCHIPRES);
  regWr(rUSBCTL, 0x00);

  int32_t now;
  uint32_t expires = micros() + 65535;

  // Enable full-duplex SPI so we can read rUSBIRQ
  regWr(rPINCTL, bmFDUPSPI);
  while ((int32_t)(micros() - expires) < 0L) {
    if ((regRd(rUSBIRQ) & bmOSCOKIRQ)) {
      break;
    }
  }
  now = (int32_t)(micros() - expires);
  if (now < 0L) {
    i = 65535 + now; // Note this subtracts, as now is negative
  }
  return (i);
}

void UHS_NI MAX3421E_HOST::VBUS_changed() {
  /* modify USB task state because Vbus changed or unknown */
  uint8_t speed = 1;
  //printf("\r\n\r\n\r\n\r\nSTATE %2.2x -> ", usb_task_state);
  switch (vbusState) {
    case LSHOST: // Low speed
    speed = 0;
    // Intentional fall-through
    case FSHOST: // Full speed
      // Start device initialization if we are not initializing
      // Resets to the device cause an IRQ
      // usb_task_state == UHS_USB_HOST_STATE_RESET_NOT_COMPLETE;
      //if ((usb_task_state & UHS_USB_HOST_STATE_MASK) != UHS_USB_HOST_STATE_DETACHED) {
      ReleaseChildren();
      if (!doingreset) {
        if (usb_task_state == UHS_USB_HOST_STATE_RESET_NOT_COMPLETE) {
          usb_task_state = UHS_USB_HOST_STATE_WAIT_BUS_READY;
        } else if (usb_task_state != UHS_USB_HOST_STATE_WAIT_BUS_READY) {
          usb_task_state = UHS_USB_HOST_STATE_DEBOUNCE;
        }
      }
      sof_countdown = 0;
    break;
    case SE1: // illegal state
      sof_countdown = 0;
      doingreset = false;
      ReleaseChildren();
      usb_task_state = UHS_USB_HOST_STATE_ILLEGAL;
      break;
    case SE0: // disconnected
    default:
      sof_countdown = 0;
      doingreset = false;
      ReleaseChildren();
      usb_task_state = UHS_USB_HOST_STATE_IDLE;
      break;
  }
  usb_host_speed = speed;
  //printf("0x%2.2x\r\n\r\n\r\n\r\n", usb_task_state);
  return;
}

/**
 * Probe bus to determine device presence and speed,
 * then switch host to detected speed.
 */
void UHS_NI MAX3421E_HOST::busprobe() {
  uint8_t bus_sample;
  uint8_t tmpdata;
  bus_sample = regRd(rHRSL); // Get J,K status
  bus_sample &= (bmJSTATUS | bmKSTATUS); // zero the rest of the byte
  switch (bus_sample) { // start full-speed or low-speed host
    case bmJSTATUS:
      // Serial.println("J");
      if ((regRd(rMODE) & bmLOWSPEED) == 0) {
      regWr(rMODE, MODE_FS_HOST); // start full-speed host
      vbusState = FSHOST;
      } else {
      regWr(rMODE, MODE_LS_HOST); // start low-speed host
      vbusState = LSHOST;
      }
      #ifdef USB_HOST_MANUAL_POLL
          enable_frame_irq(true);
      #endif
      tmpdata = regRd(rMODE) | bmSOFKAENAB; // start SOF generation
      regWr(rHIRQ, bmFRAMEIRQ); // see data sheet.
      regWr(rMODE, tmpdata);
      break;
    case bmKSTATUS:
      // Serial.println("K");
      if ((regRd(rMODE) & bmLOWSPEED) == 0) {
      regWr(rMODE, MODE_LS_HOST); // start low-speed host
      vbusState = LSHOST;
      } else {
      regWr(rMODE, MODE_FS_HOST); // start full-speed host
      vbusState = FSHOST;
      }
      #ifdef USB_HOST_MANUAL_POLL
        enable_frame_irq(true);
      #endif
      tmpdata = regRd(rMODE) | bmSOFKAENAB; // start SOF generation
      regWr(rHIRQ, bmFRAMEIRQ); // see data sheet.
      regWr(rMODE, tmpdata);
      break;
    case bmSE1: // illegal state
      // Serial.println("I");
      regWr(rMODE, bmDPPULLDN | bmDMPULLDN | bmHOST);
      vbusState = SE1;
      // sofevent = false;
      break;
    case bmSE0: // disconnected state
      // Serial.println("D");
      regWr(rMODE, bmDPPULLDN | bmDMPULLDN | bmHOST);
      vbusState = SE0;
      // sofevent = false;
      break;
  } // end switch ( bus_sample )
}

/**
 * Initialize USB hardware, turn on VBUS
 *
 * @param mseconds Delay energizing VBUS after mseconds, A value of INT16_MIN means no delay.
 * @return 0 on success, -1 on error
 */
int16_t UHS_NI MAX3421E_HOST::Init(int16_t mseconds) {
  usb_task_state = UHS_USB_HOST_STATE_INITIALIZE; //set up state machine
  //Serial.print("MAX3421E 'this' USB Host @ 0x");
  //Serial.println((uint32_t)this, HEX);
  //Serial.print("MAX3421E 'this' USB Host Address Pool @ 0x");
  //Serial.println((uint32_t)GetAddressPool(), HEX);
  Init_dyn_SWI();
  UHS_printf_HELPER_init();
  noInterrupts();
  #ifdef ARDUINO_AVR_ADK
    // For Mega ADK, which has a Max3421e on-board,
    // set MAX_RESET to output mode, and then set it to HIGH
    // PORTJ bit 2
    if (irq_pin == 54) {
      DDRJ |= 0x04; // output
      PORTJ |= 0x04; // HIGH
    }
  #endif
  SPIclass.begin();
  #ifdef ARDUINO_AVR_ADK
    if (irq_pin == 54) {
      DDRE &= ~0x20; // input
      PORTE |= 0x20; // pullup
    } else
  #endif
  pinMode(irq_pin, INPUT_PULLUP);
  //UHS_PIN_WRITE(irq_pin, HIGH);
  pinMode(ss_pin, OUTPUT);
  MARLIN_UHS_WRITE_SS(HIGH);

  #if PIN_EXISTS(USB_HOST_SHIELD_TIMING)
    pinMode(USB_HOST_SHIELD_TIMING_PIN, OUTPUT);
    // My counter/timer can't work on an inverted gate signal
    // so we gate using a high pulse -- AJK
    UHS_PIN_WRITE(USB_HOST_SHIELD_TIMING_PIN, LOW);
  #endif
  interrupts();

  #if USB_HOST_SHIELD_USE_ISR
    int intr = digitalPinToInterrupt(irq_pin);
    if (intr == NOT_AN_INTERRUPT) {
      #ifdef ARDUINO_AVR_ADK
        if (irq_pin == 54)
          intr = 6;
        else
      #endif
      return (-2);
    }
    SPIclass.usingInterrupt(intr);
  #else
    SPIclass.usingInterrupt(255);
  #endif
  #ifndef NO_AUTO_SPEED
    // test to get to reset acceptance.
    uint32_t spd = UHS_MAX3421E_SPD;
    again:
      MAX3421E_SPI_Settings = SPISettings(spd, MSBFIRST, SPI_MODE0);
    if (reset() == 0) {
      MAX_HOST_DEBUG(PSTR("Fail SPI speed %lu\r\n"), spd);
      if (spd > 1999999) {
        spd -= 1000000;
        goto again;
      }
      return (-1);
    } else {
      // reset passes, does 64k?
      uint8_t sample_wr = 0;
      uint8_t sample_rd = 0;
      uint8_t gpinpol_copy = regRd(rGPINPOL);
      for (uint16_t j = 0; j < 65535; j++) {
        regWr(rGPINPOL, sample_wr);
        sample_rd = regRd(rGPINPOL);
        if (sample_rd != sample_wr) {
          MAX_HOST_DEBUG(PSTR("Fail SPI speed %lu\r\n"), spd);
          if (spd > 1999999) {
            spd -= 1000000;
            goto again;
          }
          return (-1);
        }
        sample_wr++;
      }
      regWr(rGPINPOL, gpinpol_copy);
    }

    MAX_HOST_DEBUG(PSTR("Pass SPI speed %lu\r\n"), spd);
  #endif

  if (reset() == 0) { // OSCOKIRQ hasn't asserted in time
    MAX_HOST_DEBUG(PSTR("OSCOKIRQ hasn't asserted in time"));
    return ( -1);
  }

  /* MAX3421E - full-duplex SPI, interrupt kind, vbus off */
  regWr(rPINCTL, (bmFDUPSPI | bmIRQ_SENSE | GPX_VBDET));

  // Delay a minimum of 1 second to ensure any capacitors are drained.
  // 1 second is required to make sure we do not smoke a Microdrive!
  if (mseconds != INT16_MIN) {
    if (mseconds < 1000) mseconds = 1000;
    delay(mseconds); // We can't depend on SOF timer here.
  }

  regWr(rMODE, bmDPPULLDN | bmDMPULLDN | bmHOST); // set pull-downs, Host

  // Enable interrupts on the MAX3421e
  regWr(rHIEN, IRQ_CHECK_MASK);
  // Enable interrupt pin on the MAX3421e, set pulse width for edge
  regWr(rCPUCTL, (bmIE | bmPULSEWIDTH));

  /* check if device is connected */
  regWr(rHCTL, bmSAMPLEBUS); // sample USB bus
  while (!(regRd(rHCTL) & bmSAMPLEBUS)); // wait for sample operation to finish

  busprobe(); // check if anything is connected
  VBUS_changed();

  // GPX pin on. This is done here so that a change is detected if we have a switch connected.
  /* MAX3421E - full-duplex SPI, interrupt kind, vbus on */
  regWr(rPINCTL, (bmFDUPSPI | bmIRQ_SENSE));
  regWr(rHIRQ, bmBUSEVENTIRQ); // see data sheet.
  regWr(rHCTL, bmBUSRST); // issue bus reset to force generate yet another possible IRQ

  #if USB_HOST_SHIELD_USE_ISR
    // Attach ISR to service IRQ from MAX3421e
    noInterrupts();
    if (irq_pin & 1) {
      ISRodd = this;
      attachInterrupt(UHS_GET_DPI(irq_pin), call_ISRodd, IRQ_SENSE);
    } else {
      ISReven = this;
      attachInterrupt(UHS_GET_DPI(irq_pin), call_ISReven, IRQ_SENSE);
    }
    interrupts();
  #endif
  //printf("\r\nrPINCTL 0x%2.2X\r\n", rPINCTL);
  //printf("rCPUCTL 0x%2.2X\r\n", rCPUCTL);
  //printf("rHIEN 0x%2.2X\r\n", rHIEN);
  //printf("irq_pin %i\r\n", irq_pin);
  return 0;
}

/**
 * Setup UHS_EpInfo structure
 *
 * @param addr USB device address
 * @param ep Endpoint
 * @param ppep pointer to the pointer to a valid UHS_EpInfo structure
 * @param nak_limit how many NAKs before aborting
 * @return 0 on success
 */
uint8_t UHS_NI MAX3421E_HOST::SetAddress(uint8_t addr, uint8_t ep, UHS_EpInfo **ppep, uint16_t &nak_limit) {
  UHS_Device *p = addrPool.GetUsbDevicePtr(addr);

  if (!p)
    return UHS_HOST_ERROR_NO_ADDRESS_IN_POOL;

  if (!p->epinfo)
    return UHS_HOST_ERROR_NULL_EPINFO;

  *ppep = getEpInfoEntry(addr, ep);

  if (!*ppep)
    return UHS_HOST_ERROR_NO_ENDPOINT_IN_TABLE;

  nak_limit = (0x0001UL << (((*ppep)->bmNakPower > UHS_USB_NAK_MAX_POWER) ? UHS_USB_NAK_MAX_POWER : (*ppep)->bmNakPower));
  nak_limit--;
  /*
  USBTRACE2("\r\nAddress: ", addr);
  USBTRACE2(" EP: ", ep);
  USBTRACE2(" NAK Power: ",(*ppep)->bmNakPower);
  USBTRACE2(" NAK Limit: ", nak_limit);
  USBTRACE("\r\n");
  */
  regWr(rPERADDR, addr); // set peripheral address

  uint8_t mode = regRd(rMODE);

  //Serial.print("\r\nMode: ");
  //Serial.println( mode, HEX);
  //Serial.print("\r\nLS: ");
  //Serial.println(p->speed, HEX);

  // Set bmLOWSPEED and bmHUBPRE in case of low-speed device, reset them otherwise
  regWr(rMODE, (p->speed) ? mode & ~(bmHUBPRE | bmLOWSPEED) : mode | bmLOWSPEED | hub_present);

  return 0;
}

/**
 * Receive a packet
 *
 * @param pep pointer to a valid UHS_EpInfo structure
 * @param nak_limit how many NAKs before aborting
 * @param nbytesptr pointer to maximum number of bytes of data to receive
 * @param data pointer to data buffer
 * @return 0 on success
 */
uint8_t UHS_NI MAX3421E_HOST::InTransfer(UHS_EpInfo *pep, uint16_t nak_limit, uint16_t *nbytesptr, uint8_t *data) {
  uint8_t rcode = 0;
  uint8_t pktsize;

  uint16_t nbytes = *nbytesptr;
  MAX_HOST_DEBUG(PSTR("Requesting %i bytes "), nbytes);
  uint8_t maxpktsize = pep->maxPktSize;

  *nbytesptr = 0;
  regWr(rHCTL, (pep->bmRcvToggle) ? bmRCVTOG1 : bmRCVTOG0); // set toggle value

  // use a 'break' to exit this loop
  while (1) {
    rcode = dispatchPkt(MAX3421E_tokIN, pep->epAddr, nak_limit); // IN packet to EP-'endpoint'. Function takes care of NAKS.
    #if 0
      // This issue should be resolved now.
      if (rcode == UHS_HOST_ERROR_TOGERR) {
      //MAX_HOST_DEBUG(PSTR("toggle wrong\r\n"));
      // yes, we flip it wrong here so that next time it is actually correct!
      pep->bmRcvToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 0 : 1;
      regWr(rHCTL, (pep->bmRcvToggle) ? bmRCVTOG1 : bmRCVTOG0); // set toggle value
      continue;
      }
    #endif
    if (rcode) {
      //MAX_HOST_DEBUG(PSTR(">>>>>>>> Problem! dispatchPkt %2.2x\r\n"), rcode);
      break; // should be 0, indicating ACK. Else return error code.
    }
    /* check for RCVDAVIRQ and generate error if not present */
    /* the only case when absence of RCVDAVIRQ makes sense is when toggle error occurred. Need to add handling for that */
    if ((regRd(rHIRQ) & bmRCVDAVIRQ) == 0) {
      //MAX_HOST_DEBUG(PSTR(">>>>>>>> Problem! NO RCVDAVIRQ!\r\n"));
      rcode = 0xF0; // receive error
      break;
    }
    pktsize = regRd(rRCVBC); // number of received bytes
    MAX_HOST_DEBUG(PSTR("Got %i bytes \r\n"), pktsize);

    if (pktsize > nbytes) { // certain devices send more than asked
      //MAX_HOST_DEBUG(PSTR(">>>>>>>> Warning: wanted %i bytes but got %i.\r\n"), nbytes, pktsize);
      pktsize = nbytes;
    }

    int16_t mem_left = (int16_t)nbytes - *((int16_t*)nbytesptr);

    if (mem_left < 0)
      mem_left = 0;

    data = bytesRd(rRCVFIFO, ((pktsize > mem_left) ? mem_left : pktsize), data);

    regWr(rHIRQ, bmRCVDAVIRQ); // Clear the IRQ & free the buffer
    *nbytesptr += pktsize; // add this packet's byte count to total transfer length

    /* The transfer is complete under two conditions:           */
    /* 1. The device sent a short packet (L.T. maxPacketSize)   */
    /* 2. 'nbytes' have been transferred.                       */
    if ((pktsize < maxpktsize) || (*nbytesptr >= nbytes)) { // have we transferred 'nbytes' bytes?
      // Save toggle value
      pep->bmRcvToggle = ((regRd(rHRSL) & bmRCVTOGRD)) ? 1 : 0;
      //MAX_HOST_DEBUG(PSTR("\r\n"));
      rcode = 0;
      break;
    } // if
  } // while( 1 )
  return (rcode);
}

/**
 * Transmit a packet
 *
 * @param pep pointer to a valid UHS_EpInfo structure
 * @param nak_limit how many NAKs before aborting
 * @param nbytes number of bytes of data to send
 * @param data pointer to data buffer
 * @return 0 on success
 */
uint8_t UHS_NI MAX3421E_HOST::OutTransfer(UHS_EpInfo *pep, uint16_t nak_limit, uint16_t nbytes, uint8_t *data) {
  uint8_t rcode = UHS_HOST_ERROR_NONE;
  uint8_t retry_count;
  uint8_t *data_p = data; // local copy of the data pointer
  uint16_t bytes_tosend;
  uint16_t nak_count;
  uint16_t bytes_left = nbytes;

  uint8_t maxpktsize = pep->maxPktSize;

  if (maxpktsize < 1 || maxpktsize > 64)
  return UHS_HOST_ERROR_BAD_MAX_PACKET_SIZE;

  unsigned long timeout = millis() + UHS_HOST_TRANSFER_MAX_MS;

  regWr(rHCTL, (pep->bmSndToggle) ? bmSNDTOG1 : bmSNDTOG0); // set toggle value

  while (bytes_left) {
    SYSTEM_OR_SPECIAL_YIELD();
    retry_count = 0;
    nak_count = 0;
    bytes_tosend = (bytes_left >= maxpktsize) ? maxpktsize : bytes_left;
    bytesWr(rSNDFIFO, bytes_tosend, data_p); // filling output FIFO
    regWr(rSNDBC, bytes_tosend); // set number of bytes
    regWr(rHXFR, (MAX3421E_tokOUT | pep->epAddr)); // dispatch packet
    while (!(regRd(rHIRQ) & bmHXFRDNIRQ)); // wait for the completion IRQ
    regWr(rHIRQ, bmHXFRDNIRQ); // clear IRQ
    rcode = (regRd(rHRSL) & 0x0F);

    while (rcode && ((long)(millis() - timeout) < 0L)) {
      switch (rcode) {
        case UHS_HOST_ERROR_NAK:
          nak_count++;
          if (nak_limit && (nak_count == nak_limit))
            goto breakout;
          break;
        case UHS_HOST_ERROR_TIMEOUT:
          retry_count++;
          if (retry_count == UHS_HOST_TRANSFER_RETRY_MAXIMUM)
            goto breakout;
          break;
        case UHS_HOST_ERROR_TOGERR:
          // yes, we flip it wrong here so that next time it is actually correct!
          pep->bmSndToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 0 : 1;
          regWr(rHCTL, (pep->bmSndToggle) ? bmSNDTOG1 : bmSNDTOG0); // set toggle value
          break;
        default:
          goto breakout;
      } // switch (rcode

      /* process NAK according to Host out NAK bug */
      regWr(rSNDBC, 0);
      regWr(rSNDFIFO, *data_p);
      regWr(rSNDBC, bytes_tosend);
      regWr(rHXFR, (MAX3421E_tokOUT | pep->epAddr)); // dispatch packet
      while (!(regRd(rHIRQ) & bmHXFRDNIRQ)); // wait for the completion IRQ
      regWr(rHIRQ, bmHXFRDNIRQ); // clear IRQ
      rcode = (regRd(rHRSL) & 0x0F);
      SYSTEM_OR_SPECIAL_YIELD();
    } // while (rcode && ....
    bytes_left -= bytes_tosend;
    data_p += bytes_tosend;
  } // while (bytes_left...
  breakout:

  pep->bmSndToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 1 : 0; // bmSNDTOG1 : bmSNDTOG0;  // update toggle
  return (rcode); // should be 0 in all cases
}

/**
 * Send the actual packet.
 *
 * @param token
 * @param ep Endpoint
 * @param nak_limit how many NAKs before aborting, 0 == exit after timeout
 * @return 0 on success, 0xFF indicates NAK timeout. @see
 */
/* Assumes peripheral address is set and relevant buffer is loaded/empty       */
/* If NAK, tries to re-send up to nak_limit times                                                   */
/* If nak_limit == 0, do not count NAKs, exit after timeout                                         */
/* If bus timeout, re-sends up to USB_RETRY_LIMIT times                                             */

/* return codes 0x00-0x0F are HRSLT( 0x00 being success ), 0xFF means timeout                       */
uint8_t UHS_NI MAX3421E_HOST::dispatchPkt(uint8_t token, uint8_t ep, uint16_t nak_limit) {
  unsigned long timeout = millis() + UHS_HOST_TRANSFER_MAX_MS;
  uint8_t tmpdata;
  uint8_t rcode = UHS_HOST_ERROR_NONE;
  uint8_t retry_count = 0;
  uint16_t nak_count = 0;

  for (;;) {
    regWr(rHXFR, (token | ep)); // launch the transfer
    while (long(millis() - timeout) < 0L) { // wait for transfer completion
      SYSTEM_OR_SPECIAL_YIELD();
      tmpdata = regRd(rHIRQ);

      if (tmpdata & bmHXFRDNIRQ) {
        regWr(rHIRQ, bmHXFRDNIRQ); // clear the interrupt
        //rcode = 0x00;
        break;
      } // if (tmpdata & bmHXFRDNIRQ

    } // while (millis() < timeout

    rcode = (regRd(rHRSL) & 0x0F); // analyze transfer result

    switch (rcode) {
      case UHS_HOST_ERROR_NAK:
        nak_count++;
        if (nak_limit && (nak_count == nak_limit))
          return (rcode);
        delayMicroseconds(200);
        break;
      case UHS_HOST_ERROR_TIMEOUT:
        retry_count++;
        if (retry_count == UHS_HOST_TRANSFER_RETRY_MAXIMUM)
          return (rcode);
        break;
      default:
        return (rcode);
    } // switch (rcode)
  }
}

//
// NULL is error, we don't need to know the reason.
//

UHS_EpInfo * UHS_NI MAX3421E_HOST::ctrlReqOpen(uint8_t addr, uint64_t Request, uint8_t *dataptr) {
  uint8_t rcode;
  UHS_EpInfo *pep = NULL;
  uint16_t nak_limit = 0;
  rcode = SetAddress(addr, 0, &pep, nak_limit);

  if (!rcode) {

    bytesWr(rSUDFIFO, 8, (uint8_t*)(&Request)); // transfer to setup packet FIFO

    rcode = dispatchPkt(MAX3421E_tokSETUP, 0, nak_limit); // dispatch packet
    if (!rcode) {
      if (dataptr != NULL) {
        if (((Request)/* bmReqType*/ & 0x80) == 0x80) {
          pep->bmRcvToggle = 1; //bmRCVTOG1;
        } else {
          pep->bmSndToggle = 1; //bmSNDTOG1;
        }
      }
    } else {
      pep = NULL;
    }
  }
  return pep;
}

uint8_t UHS_NI MAX3421E_HOST::ctrlReqRead(UHS_EpInfo *pep, uint16_t *left, uint16_t *read, uint16_t nbytes, uint8_t *dataptr) {
  *read = 0;
  uint16_t nak_limit = 0;
  MAX_HOST_DEBUG(PSTR("ctrlReqRead left: %i\r\n"), *left);
  if (*left) {
    again:
    *read = nbytes;
    uint8_t rcode = InTransfer(pep, nak_limit, read, dataptr);
    if (rcode == UHS_HOST_ERROR_TOGERR) {
      // yes, we flip it wrong here so that next time it is actually correct!
      pep->bmRcvToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 0 : 1;
      goto again;
    }

    if (rcode) {
      MAX_HOST_DEBUG(PSTR("ctrlReqRead ERROR: %2.2x, left: %i, read %i\r\n"), rcode, *left, *read);
      return rcode;
    }
    *left -= *read;
    MAX_HOST_DEBUG(PSTR("ctrlReqRead left: %i, read %i\r\n"), *left, *read);
  }
  return 0;
}

uint8_t UHS_NI MAX3421E_HOST::ctrlReqClose(UHS_EpInfo *pep, uint8_t bmReqType, uint16_t left, uint16_t nbytes, uint8_t *dataptr) {
  uint8_t rcode = 0;

  //MAX_HOST_DEBUG(PSTR("Closing"));
  if (((bmReqType & 0x80) == 0x80) && pep && left && dataptr) {
    MAX_HOST_DEBUG(PSTR("ctrlReqRead Sinking %i\r\n"), left);
    // If reading, sink the rest of the data.
    while (left) {
      uint16_t read = nbytes;
      rcode = InTransfer(pep, 0, &read, dataptr);
      if (rcode == UHS_HOST_ERROR_TOGERR) {
        // yes, we flip it wrong here so that next time it is actually correct!
        pep->bmRcvToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 0 : 1;
        continue;
      }
      if (rcode) break;
      left -= read;
      if (read < nbytes) break;
    }
  }
  if (!rcode) {
    //Serial.println("Dispatching");
    rcode = dispatchPkt(((bmReqType & 0x80) == 0x80) ? MAX3421E_tokOUTHS : MAX3421E_tokINHS, 0, 0); //GET if direction
  //} else {
    //Serial.println("Bypassed Dispatch");
  }
  return rcode;
}

/**
 * Bottom half of the ISR task
 */
void UHS_NI MAX3421E_HOST::ISRbottom() {
  uint8_t x;
  //        Serial.print("Enter ");
  //        Serial.print((uint32_t)this,HEX);
  //        Serial.print(" ");
  //        Serial.println(usb_task_state, HEX);

  DDSB();
  if (condet) {
    VBUS_changed();
    #if USB_HOST_SHIELD_USE_ISR
      noInterrupts();
    #endif
    condet = false;
    #if USB_HOST_SHIELD_USE_ISR
      interrupts();
    #endif
  }
  switch (usb_task_state) {
    case UHS_USB_HOST_STATE_INITIALIZE:
      // should never happen...
      MAX_HOST_DEBUG(PSTR("UHS_USB_HOST_STATE_INITIALIZE\r\n"));
      busprobe();
      VBUS_changed();
      break;
    case UHS_USB_HOST_STATE_DEBOUNCE:
      MAX_HOST_DEBUG(PSTR("UHS_USB_HOST_STATE_DEBOUNCE\r\n"));
      // This seems to not be needed. The host controller has debounce built in.
      sof_countdown = UHS_HOST_DEBOUNCE_DELAY_MS;
      usb_task_state = UHS_USB_HOST_STATE_DEBOUNCE_NOT_COMPLETE;
      break;
    case UHS_USB_HOST_STATE_DEBOUNCE_NOT_COMPLETE:
      MAX_HOST_DEBUG(PSTR("UHS_USB_HOST_STATE_DEBOUNCE_NOT_COMPLETE\r\n"));
      if (!sof_countdown) usb_task_state = UHS_USB_HOST_STATE_RESET_DEVICE;
      break;
    case UHS_USB_HOST_STATE_RESET_DEVICE:
      MAX_HOST_DEBUG(PSTR("UHS_USB_HOST_STATE_RESET_DEVICE\r\n"));
      busevent = true;
      usb_task_state = UHS_USB_HOST_STATE_RESET_NOT_COMPLETE;
      regWr(rHIRQ, bmBUSEVENTIRQ); // see data sheet.
      regWr(rHCTL, bmBUSRST); // issue bus reset
      break;
    case UHS_USB_HOST_STATE_RESET_NOT_COMPLETE:
      MAX_HOST_DEBUG(PSTR("UHS_USB_HOST_STATE_RESET_NOT_COMPLETE\r\n"));
      if (!busevent) usb_task_state = UHS_USB_HOST_STATE_WAIT_BUS_READY;
      break;
    case UHS_USB_HOST_STATE_WAIT_BUS_READY:
      MAX_HOST_DEBUG(PSTR("UHS_USB_HOST_STATE_WAIT_BUS_READY\r\n"));
      usb_task_state = UHS_USB_HOST_STATE_CONFIGURING;
      break; // don't fall through

    case UHS_USB_HOST_STATE_CONFIGURING:
      usb_task_state = UHS_USB_HOST_STATE_CHECK;
      x = Configuring(0, 1, usb_host_speed);
      usb_error = x;
      if (usb_task_state == UHS_USB_HOST_STATE_CHECK) {
        if (x) {
          MAX_HOST_DEBUG(PSTR("Error 0x%2.2x"), x);
          if (x == UHS_HOST_ERROR_JERR) {
            usb_task_state = UHS_USB_HOST_STATE_IDLE;
          } else if (x != UHS_HOST_ERROR_DEVICE_INIT_INCOMPLETE) {
            usb_error = x;
            usb_task_state = UHS_USB_HOST_STATE_ERROR;
          }
        } else
          usb_task_state = UHS_USB_HOST_STATE_CONFIGURING_DONE;
      }
      break;

    case UHS_USB_HOST_STATE_CHECK:
      // Serial.println((uint32_t)__builtin_return_address(0), HEX);
      break;
    case UHS_USB_HOST_STATE_CONFIGURING_DONE:
      usb_task_state = UHS_USB_HOST_STATE_RUNNING;
      break;

    #ifdef USB_HOST_MANUAL_POLL
      case UHS_USB_HOST_STATE_RUNNING:
      case UHS_USB_HOST_STATE_ERROR:
      case UHS_USB_HOST_STATE_IDLE:
      case UHS_USB_HOST_STATE_ILLEGAL:
        enable_frame_irq(false);
        break;
    #else
      case UHS_USB_HOST_STATE_RUNNING:
        Poll_Others();
        for (x = 0; (usb_task_state == UHS_USB_HOST_STATE_RUNNING) && (x < UHS_HOST_MAX_INTERFACE_DRIVERS); x++) {
          if (devConfig[x]) {
            if (devConfig[x]->bPollEnable) devConfig[x]->Poll();
          }
        }
        // fall thru
    #endif
    default:
      // Do nothing
      break;
  } // switch ( usb_task_state )
  DDSB();
  #if USB_HOST_SHIELD_USE_ISR
    if (condet) {
      VBUS_changed();
      noInterrupts();
      condet = false;
      interrupts();
    }
  #endif
  #if PIN_EXISTS(USB_HOST_SHIELD_TIMING)
    // My counter/timer can't work on an inverted gate signal
    // so we gate using a high pulse -- AJK
    UHS_PIN_WRITE(USB_HOST_SHIELD_TIMING_PIN, LOW);
  #endif
  //usb_task_polling_disabled--;
  EnablePoll();
  DDSB();
}

/* USB main task. Services the MAX3421e */
#if !USB_HOST_SHIELD_USE_ISR
  void UHS_NI MAX3421E_HOST::ISRTask() {}
  void UHS_NI MAX3421E_HOST::Task()
#else
  void UHS_NI MAX3421E_HOST::Task() {
    #ifdef USB_HOST_MANUAL_POLL
      if (usb_task_state == UHS_USB_HOST_STATE_RUNNING) {
        noInterrupts();
        for (uint8_t x = 0; x < UHS_HOST_MAX_INTERFACE_DRIVERS; x++)
          if (devConfig[x] && devConfig[x]->bPollEnable)
            devConfig[x]->Poll();
        interrupts();
      }
    #endif
  }

  void UHS_NI MAX3421E_HOST::ISRTask()
#endif
{
  DDSB();

  #ifndef SWI_IRQ_NUM
    suspend_host();
    #if USB_HOST_SHIELD_USE_ISR
      // Enable interrupts
      interrupts();
    #endif
  #endif

  counted = false;
  if (!MARLIN_UHS_READ_IRQ()) {
    uint8_t HIRQALL = regRd(rHIRQ); // determine interrupt source
    uint8_t HIRQ = HIRQALL & IRQ_CHECK_MASK;
    uint8_t HIRQ_sendback = 0x00;

    if ((HIRQ & bmCONDETIRQ) || (HIRQ & bmBUSEVENTIRQ)) {
      MAX_HOST_DEBUG
      (PSTR("\r\nBEFORE CDIRQ %s BEIRQ %s resetting %s state 0x%2.2x\r\n"),
        (HIRQ & bmCONDETIRQ) ? "T" : "F",
        (HIRQ & bmBUSEVENTIRQ) ? "T" : "F",
        doingreset ? "T" : "F",
        usb_task_state
      );
    }
    // ALWAYS happens BEFORE or WITH CONDETIRQ
    if (HIRQ & bmBUSEVENTIRQ) {
      HIRQ_sendback |= bmBUSEVENTIRQ;
      if (!doingreset) condet = true;
      busprobe();
      busevent = false;
    }

    if (HIRQ & bmCONDETIRQ) {
      HIRQ_sendback |= bmCONDETIRQ;
      if (!doingreset) condet = true;
      busprobe();
    }

    #if 1
      if ((HIRQ & bmCONDETIRQ) || (HIRQ & bmBUSEVENTIRQ)) {
        MAX_HOST_DEBUG
        (PSTR("\r\nAFTER CDIRQ %s BEIRQ %s resetting %s state 0x%2.2x\r\n"),
          (HIRQ & bmCONDETIRQ) ? "T" : "F",
          (HIRQ & bmBUSEVENTIRQ) ? "T" : "F",
          doingreset ? "T" : "F",
          usb_task_state
        );
      }
    #endif

    if (HIRQ & bmFRAMEIRQ) {
      HIRQ_sendback |= bmFRAMEIRQ;
      if (sof_countdown) {
        sof_countdown--;
        counted = true;
      }
      sofevent = false;
    }

    //MAX_HOST_DEBUG(PSTR("\r\n%s%s%s\r\n"),
    //        sof_countdown ? "T" : "F",
    //        counted ? "T" : "F",
    //        usb_task_polling_disabled? "T" : "F");
    DDSB();
    regWr(rHIRQ, HIRQ_sendback);
    #ifndef SWI_IRQ_NUM
      resume_host();
      #if USB_HOST_SHIELD_USE_ISR
        // Disable interrupts
        noInterrupts();
      #endif
    #endif
    if (!sof_countdown && !counted && !usb_task_polling_disabled) {
      DisablePoll();
      //usb_task_polling_disabled++;
      #if PIN_EXISTS(USB_HOST_SHIELD_TIMING)
        // My counter/timer can't work on an inverted gate signal
        // so we gate using a high pulse -- AJK
        UHS_PIN_WRITE(USB_HOST_SHIELD_TIMING_PIN, HIGH);
      #endif

      #ifdef SWI_IRQ_NUM
        //MAX_HOST_DEBUG(PSTR("--------------- Doing SWI ----------------"));
        exec_SWI(this);
      #else
        #if USB_HOST_SHIELD_USE_ISR
          // Enable interrupts
          interrupts();
        #endif
        ISRbottom();
      #endif /* SWI_IRQ_NUM */
    }
  }
}

#if 0
  DDSB();
#endif

#else
  #error "Never include USB_HOST_SHIELD_INLINE.h, include UHS_host.h instead"
#endif
